/*
 * @(#)XfdmIncClustering.java        1.0 2000/05/09
 *
 * This file is part of Xfuzzy 3.0, a design environment for fuzzy logic
 * based systems.
 *
 * (c) 2000 IMSE-CNM. The authors may be contacted by the email address:
 *                    xfuzzy-team@imse.cnm.es
 *
 * Xfuzzy is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by
 * the Free Software Foundation.
 *
 * Xfuzzy is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 */

package xfuzzy.xfdm.model.algorithm;

import xfuzzy.lang.*;
import xfuzzy.xfdm.model.*;
import xfuzzy.xfds.XfdsDataSet;

/**
 * Algoritmo de clustering incremental (substractive)
 * 
 * @author Francisco Jos� Moreno Velo
 *
 */
public class XfdmIncClustering extends XfdmAlgorithm {

	//----------------------------------------------------------------------------//
	//                            MIEMBROS PRIVADOS                               //
	//----------------------------------------------------------------------------//

	/**
	 * M�ximo n�mero de clusters
	 */
	private int clusters_limit;
	
	/**
	 * Radio de vecindad
	 */
	private double radius;

	/**
	 * Instancias del conjunto de datos normalizados en el intervalo [0,1]
	 */
	private double point[][];
	
	/**
	 * Clusters
	 */
	private double cluster[][];
	
	/**
	 * Potencial de cada instancia
	 */
	private double potential[];
	
	/**
	 * Potencial de cada cluster
	 */
	private double cl_potential[];

	//----------------------------------------------------------------------------//
	//                                CONSTRUCTOR                                 //
	//----------------------------------------------------------------------------//
	
	/**
	 * Constructor por defecto
	 */
	public XfdmIncClustering() {
		this.clusters_limit = 10;
		this.radius = 0.1;
	}

	/**
	 * Constructor desde la interfaz gr�fica
	 */
	public XfdmIncClustering(int limit, double radius) {
		this.clusters_limit = limit;
		this.radius = radius;
	}

	/**
	 * Constructor desde el fichero de configuraci�n
	 */
	public XfdmIncClustering(double[] param) {
		this.clusters_limit = (int) param[0];
		this.radius = (double) param[1];
	}

	//----------------------------------------------------------------------------//
	//                             M�TODOS P�BLICOS                               //
	//----------------------------------------------------------------------------//

	//----------------------------------------------------------------------------//
	// M�todos de acceso a la configuraci�n                                       //
	//----------------------------------------------------------------------------//

	/**
	 * Obtiene el valor del m�ximo n�mero de clusters permitido	
	 */
	public int getClustersLimit() {
		return this.clusters_limit;
	}

	/**
	 * Asigna el valor del m�ximo n�mero de clusters permitido
	 */
	public void setClustersLimit(int limit) {
		this.clusters_limit = limit;
	}

	/**
	 * Obtiene el valor del radio de vecindad
	 */
	public double getRadius() {
		return this.radius;
	}

	/**
	 * Asigna el valor del radio de vecindad
	 */
	public void setRadius(double radius) {
		this.radius = radius;
	}

	//----------------------------------------------------------------------------//
	// M�todos de desarrollo de XfdmAlgorithm                                     //
	//----------------------------------------------------------------------------//

	/**
	 * Obtiene un duplicado del objeto
	 */
	public Object clone() {
		return new XfdmIncClustering(clusters_limit,radius);
	}

	/**
	 * Obtiene el nombre del algoritmo
	 */
	public String toString() {
		return "Incremental Clustering (Substractive clustering)";
	}

	/**
	 * Indica si el algoritmo necesita que las variables de entrada tengan creadas
	 * las funciones de pertenencia. En caso contrario el algoritmo debe encargarse
	 * de dar contenido a los tipos de las variables de entrada.
	 */
	public boolean needInputMemFuncs() {
		return false;
	}
	
	/**
	 * Indica si el algoritmo necesita que las variables de salida tengan creadas
	 * las funciones de pertenencia. En caso contrario el algoritmo debe encargarse
	 * de dar contenido a los tipos de las variables de salida.
	 */
	public boolean needOutputMemFuncs() {
		return false;
	}

	/**
	 * Indica si el algoritmo necesita que la estructura del sistema est� creada.
	 * Este requisito es propio de los algoritmo que contienen alguna etapa de
	 * optimizaci�n param�trica.
	 * @return
	 */
	public boolean needSystemStructure() {
		return false;
	}
		
	/**
	 * Indica si el algoritmo es v�lido para problemas de clasificaci�n
	 * @return
	 */
	public boolean supportsClassification() {
		return false;
	}
	
	/**
	 * Indica si el algoritmo es v�lido para problemas de regresi�n
	 * @return
	 */
	public boolean supportsRegression() {
		return true;
	}

	/**
	 * Representaci�n en el fichero de configuraci�n
	 */
	public String toXML() {
		String eol = System.getProperty("line.separator", "\n");
		String code = "\t\t<algorithm name=\"IncClustering\" >"+eol;
		code += "\t\t\t<param name=\"clusters\" value=\""+clusters_limit+"\" ></param>"+eol;
		code += "\t\t\t<param name=\"radius\" value=\""+radius+"\" ></param>"+eol;
		code +=	"\t\t</algorithm>";
		return code;
	}

	/**
	 * Obtiene el tipo de base de conocimiento que genera el algoritmo
	 */
	public int getKnowledgeBaseKind() {
		return KnowledgeBase.RULE_SET;
	}

	/**
	 * Genera el contenido de la base de conocimiento a partir de los datos
	 */
	public void extractKnowlegde(KnowledgeBase base, XfdmConfig config) {
		this.point = createPointsFromPatterns(base, config.getDataSet());
		this.potential = createPotentials();
		this.cluster = new double[0][];
		this.cl_potential = new double[0];
		do {
			selectCluster();
			updatePotentials();
		} while(!satisfyEndCondition());

		createContent(base, config.getSystemStyle().getDefuzMethod());
	}
	
	//----------------------------------------------------------------------------//
	//                             M�TODOS PRIVADOS                               //
	//----------------------------------------------------------------------------//

	//----------------------------------------------------------------------------//
	// M�todos que desarrollan el algoritmo                                       //
	//----------------------------------------------------------------------------//

	/**
	 * Obtiene el conjunto normalizado de patrones
	 */
	private double[][] createPointsFromPatterns(KnowledgeBase base, XfdsDataSet dataset) {
		Variable[] ivar = base.getInputs();
		Variable[] ovar = base.getOutputs();
		int length = dataset.input.length;
		int width = ivar.length + ovar.length;
		double point[][] = new double[length][width];
		double min[] = new double[width];
		double max[] = new double[width];
		for(int i=0; i<width; i++) {
			if(i<ivar.length) {
				min[i] = ivar[i].getType().min();
				max[i] = ivar[i].getType().max();
			} else {
				min[i] = ovar[i-ivar.length].getType().min();
				max[i] = ovar[i-ivar.length].getType().max();
			}
		}

		for(int i=0; i<length; i++) for(int j=0; j<width; j++) {
			if(j<ivar.length) {
				point[i][j] = (dataset.input[i][j]-min[j])/(max[j]-min[j]);
			} else {
				point[i][j] =(dataset.output[i][j-ivar.length]-min[j])/(max[j]-min[j]);
			}
		}
		return point;
	}

	/**
	 * Genera los potenciales asociados a cada punto
	 */
	private double[] createPotentials() {
		double potential[] = new double[point.length];
		double alpha = 4/(radius*radius);
		for(int i=0; i<point.length; i++) for(int j=0; j<point.length; j++) {
			if(i!=j) potential[i] += Math.exp(-alpha*distance(point[i],point[j]));
		}
		return potential;
	}

	/**
	 * A�ade como cluster el punto de mayor potencial
	 */
	private void selectCluster() {
		int index = -1;
		double max = 0;
		for(int i=0; i<potential.length; i++) {
			if(potential[i]>max) { max = potential[i]; index = i; }
		}
		if(index <0) return;

		double[][] ac = new double[cluster.length+1][];
		System.arraycopy(cluster,0,ac,0,cluster.length);
		ac[cluster.length] = point[index];
		cluster = ac;

		double[] acp = new double[cl_potential.length+1];
		System.arraycopy(cl_potential,0,acp,0,cl_potential.length);
		acp[cl_potential.length] = potential[index];
		cl_potential = acp;

		double[][] ap = new double[point.length-1][];
		System.arraycopy(point,0,ap,0,index);
		System.arraycopy(point,index+1,ap,index,point.length-index-1);
		point = ap;

		double[] app = new double[potential.length-1];
		System.arraycopy(potential,0,app,0,index);
		System.arraycopy(potential,index+1,app,index,potential.length - index -1);
		potential = app;
	}

	/**
	 * Actualiza los potenciales considerando el ultimo cluster
	 */
	private void updatePotentials() {
		double[] lastcluster = cluster[cluster.length-1];
		double lastpotential = cl_potential[cluster.length-1];
		double rb = 1.25*radius;
		double beta = -4/(rb*rb);
		for(int i=0; i<point.length; i++) {
			potential[i] -= lastpotential*Math.exp(beta*distance(point[i],lastcluster)); 
		}
	}

	/**
	 * Verifica si se han cumplido las condiciones de t�rmino
	 */
	private boolean satisfyEndCondition() {
		if(cluster.length >= clusters_limit) return true;
		for(int i=0; i<potential.length; i++) {
			if(potential[i] > 0.15*cl_potential[0]) return false;
		}
		return false;
	}

	/**
	 * Calcula la distancia euclidea entre dos puntos (al cuadrado)
	 */
	private double distance(double[] x, double[] y) {
		double dist = 0;
		for(int i=0; i<x.length; i++) dist += (x[i]-y[i])*(x[i]-y[i]);
		return dist;
	}

	//----------------------------------------------------------------------------//
	// M�todos que generan el contenido del sistema                               //
	//----------------------------------------------------------------------------//

	/**
	 * Genera el contenido del sistema a partir de los clusters
	 */
	private void createContent(KnowledgeBase base, int defuz) {
		Variable[] ivar = base.getInputs();
		Variable[] ovar = base.getOutputs();
		for(int i=0; i<ivar.length; i++) createBells(ivar[i].getType(),i);
		for(int i=0; i<ovar.length; i++) {
			switch(defuz) {
				case XfdmSystemStyle.FUZZYMEAN:
					createSingletons(ovar[i].getType(),ivar.length + i);
					break;
				case XfdmSystemStyle.WEIGHTED:
					createBells(ovar[i].getType(),ivar.length + i);
					break;
				case XfdmSystemStyle.TAKAGI:
					createParametric(ovar[i].getType(),ivar.length + i, ivar.length);
					break;
			}
		}
		createRules(base);
	}

	/**
	 * Genera las reglas correspondientes a los clusters
	 */
	private void createRules(KnowledgeBase base) {
		Variable ivar[] = base.getInputs();
		Variable ovar[] = base.getOutputs();
		int is = Relation.IS;

		for(int i=0; i<cluster.length; i++) {
			LinguisticLabel pmf = ivar[0].getType().getAllMembershipFunctions()[i];
			Relation rel = Relation.create(is,null,null,ivar[0],pmf,base);
			for(int j=1; j<ivar.length; j++) {
				pmf = ivar[j].getType().getAllMembershipFunctions()[i];
				Relation nrel = Relation.create(is,null,null,ivar[j],pmf,base);
				rel = Relation.create(Relation.AND,rel,nrel,null,null,base);
			}
			Rule rule = new Rule(rel);
			for(int j=0; j<ovar.length; j++) {
				pmf = ovar[j].getType().getAllMembershipFunctions()[i];
				rule.add(new Conclusion(ovar[j],pmf,base));
			}
			((RuleSet) base).addRule(rule);
		}
	}



	//----------------------------------------------------------------------------//
	// M�todos que generan las funciones de pertenencia                           //
	//----------------------------------------------------------------------------//

	/**
	 * Crea un conjunto de singularidades
	 */
	private void createSingletons(Type type, int index) {
		Universe u = type.getUniverse();
		double min = u.min();
		double max = u.max();
		for(int i=0; i<cluster.length; i++) {
			ParamMemFunc pmf = new xfuzzy.pkg.xfl.mfunc.singleton();
			pmf.set("mf"+i, u);
			pmf.set( min + cluster[i][index]*(max-min) );
			try { type.add(pmf); } catch(XflException e) {}
		}
	}

	/**
	 * Crea un conjunto de funciones param�tricas
	 */
	private void createParametric(Type type, int index, int inputs) {
		Universe u = type.getUniverse();
		double min = u.min();
		double max = u.max();
		double param[] = new double[inputs+1];
		for(int i=0; i<cluster.length; i++) {
			ParamMemFunc pmf = new xfuzzy.pkg.xfl.mfunc.parametric();
			pmf.set("mf"+i, u);
			param[0] = min + cluster[i][index]*(max-min);
			try { pmf.set(param); type.add(pmf); } catch(XflException ex) {}
		}
	}

	/**
	 * Crea un conjunto de campanas	
	 */
	private void createBells(Type type, int index) {
		Universe u = type.getUniverse();
		double min = u.min();
		double max = u.max();
		double param[] = new double[2];
		param[1] = radius*(max - min)/2;
		for(int i=0; i<cluster.length; i++) {
			ParamMemFunc pmf = new xfuzzy.pkg.xfl.mfunc.bell();
			pmf.set("mf"+i, u);
			param[0] = min + cluster[i][index]*(max-min);
			try { pmf.set(param); type.add(pmf); } catch(XflException ex) {}
		}
	}

}

